// Copyright (c) [Year] [name of copyright holder]
// [Software Name] is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2. 
// You may obtain a copy of Mulan PSL v2 at:
//          http://license.coscl.org.cn/MulanPSL2 
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.  
// See the Mulan PSL v2 for more details.

/*!
 * \autor captainpeng
 * \date 2019-9-30
 * \version 1.0
 * \copyright
 */
#ifndef MY_UTF8_HPP
#define MY_UTF8_HPP

#include<istream>
#include"mystd.hpp"
#include"unicode.hpp"

namespace my{

    template<>
    class utf<8>{

    public:
        using streamType = std::istream;
        using strType = std::string;
        using unicodeType = unicode_t;
        using vecType = std::vector<unicodeType>;
        
    private:
        
    public:
        // step 将会返回读取了几位
        static unicodeType CStrToUnicode(const char * str, int & step){
            if(str == nullptr) return unicode::error;

            step = 0;
            const char * cstr = str;
            unicodeType ch = 0;
            unsigned char z = *cstr;
          
            int off_len = 0;
            if(z >= 0 && z < 0x80){ // [0x0, 0x7F]
                ch = z;
                step = 1;
                return ch;
            }else if(z >= 0xC0 && z < 0xE0){ // [0xC0, 0xDF]
                off_len = 6;
                ch = z - 0xC0;
            }else if(z < 0xF0){ // [0xE0, 0xEF]
                off_len = 12;
                ch = z - 0xE0;
            }else if(z < 0xF8){ // [0xF0, 0xF7]
                off_len = 18;
                ch = z - 0xF0;
            }else if(z < 0xFC){ // [0xF8, 0xFB]
                off_len = 24;
                ch = z - 0xF8;
            }else if(z <= 0xFD){ // [0xFC, 0xFD]
                off_len = 30;
                ch = z - 0xFC;
            }else{
                return unicode::error;
            }

            ch <<= off_len;
            while(off_len > 0){
                z = *(++cstr);
                if(z < 0x80){
                    step = cstr - str + 1;
                    return unicode::lack;
                }

                off_len -= 6;
                ch += ((z - 0x80)<<off_len);
            }

            step = cstr - str + 1;
            return ch;
        }

        static bool StrAddUnicode(strType & str, unicode_t ch){
            char buf[8];
            unsigned char end;

            constexpr char bit6ful1 = 0x3f;
        
            if(ch >= 0 && ch < 0x80){ // [0, 0x7F]
                buf[0] = 0x0;
                end = 0;
            }else if(ch < 0x800){ // [0x80, 0x7FF]
                buf[0] = 0xC0;
                end = 1;
            }else if(ch < 0x10000){ // [0x800, 0xFFFF]
                buf[0] = 0xE0;
                end = 2;
            }else if(ch < 0x200000){ // [0x1 0000, 0x1F 
                buf[0] = 0xF0;
                end = 3;
            }else if(ch < 0x4000000){ // [0x20 0000, 0x3FF FFFF]
                buf[0] = 0xF8;
                end = 4;
            }else if(ch <= 0x7FFFFFFF){ // [0x400 0000, 0x7FFF FFFF]
                buf[0] = 0xFC;
                end = 5;
            }else return false;

            
            buf[end+1] = '\0';
            while(end != 0){
                buf[end] = 0x80 + (ch & bit6ful1);
                ch >>= 6;
                --end;
            }
            
            buf[0] += ch;
            str.append(buf);
            return true;
        }
        
        static strType UnicodeToStr(unicodeType ch){
            strType str;
            StrAddUnicode(str, ch);
            return str;
        }

        // 若 string 中的 utf8 字符不止一个，则仅仅返回第一个
        static unicodeType StrToUnicode(const strType & str){
            if(str.size() == 0) return 0;
                                
            int tmp;
            unicodeType ch = CStrToUnicode(str.c_str(), tmp);
            // std::cout<<"str.size() = "<<str.size()<<","<<"step = "<<s<<std::endl;
            return ch;
        }

        static vecType StrToUnicodeVec(const strType & str){
            vecType vec;
            vec.reserve(str.size() + 1);
            const char * cstr = str.c_str();
            int step = 0;
            unicodeType ch;
            while((*cstr) != 0){
                ch = CStrToUnicode(cstr, step);
                cstr += (step == 0)?1:step;
                if(ch != unicode::error)
                    vec.push_back(ch);
            }
            vec.push_back(0);
            return vec;
        }

        static void StrAddUnicodeVec(strType & str, const vecType & vec){
            for(size_t i = 0; i < vec.size(); ++i){
                StrAddUnicode(str, vec[i]);
            }
        }
        
        static strType UnicodeVecToStr(const vecType & vec){
            strType str;
            StrAddUnicodeVec(str, vec);
            return str;
        }
        
    };

    using utf8 = utf<8>;
}

#endif
